package com.showcase.cloud.gateway.filters;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.showcase.cloud.gateway.config.IgnoreUrlsConfig;
import com.showcase.cloud.gateway.utils.TokenContexHolder;
import com.sun.showcase.client.domain.basic.ResourceInfo;
import com.sun.showcase.client.domain.basic.UserInfo;
import com.sun.showcase.pojo.Result;
import com.sun.showcase.utils.JwtTokenUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import org.springframework.util.PathMatcher;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

@Component
public class JwtTokenFilter implements GlobalFilter, Ordered {
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenFilter.class);
    private static final String LOGIN_URL="/login";
    private static final String NOT_AUTH_URL="/noAuth";
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Autowired
    private com.showcase.cloud.gateway.feign.UserFeignService userFeignService;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private IgnoreUrlsConfig ignoreUrlsConfig;
    @Value("${jwt.header}")
    private String tokenHeader;
    @Value("${jwt.headName}")
    private String tokenHead;
    @Value("${service.basic.path}")
    private String basicPath;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String url = exchange.getRequest().getURI().getPath();

        //跳过不需要验证的路径
        PathMatcher pathMatcher = new AntPathMatcher();
        for (String path : ignoreUrlsConfig.getUrls()) {
            if(pathMatcher.match(path,url)){
                return chain.filter(exchange);
            }
        }
        String authHeader=getToken(request);
        if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
            String authToken = authHeader.substring(this.tokenHead.length()+1);// The part after "Bearer "
            String username = jwtTokenUtil.getUserNameFromToken(authToken);
            //request.mutate().
            String finalAuthHeader = authHeader;
            /*Consumer<HttpHeaders> httpHeaders = httpHeader -> {
                httpHeader.set(tokenHeader, finalAuthHeader);
            };
            request.mutate().headers(httpHeaders);*/
            request.mutate().header(tokenHeader,finalAuthHeader);
            LOGGER.info("checking username:{}", username);
            if (username != null) {
                TokenContexHolder.put(finalAuthHeader);
                Result<UserInfo> r = this.userFeignService.loadUserByUsername(username);
                if(!r.isSuccess()){
                    LOGGER.error(r.getMsg());
                    return getVoidMono(exchange,r);
                }
                UserInfo userDetails = (UserInfo)r.getData();
                if (jwtTokenUtil.validateToken(authToken, userDetails.getName())) {
                    Result<List<ResourceInfo>> pms = userFeignService.allPermissions(userDetails.getId());
                    if(!pms.isSuccess() || !this.checkPermissions(pms.getData(),url)){
                        LOGGER.info("{}无权访问地址:{}", username,url);
                        LOGGER.error(pms.getMsg());
                        return doRedirct(exchange,"/"+basicPath+NOT_AUTH_URL);
                    }
                }else {

                    response.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
                    response.getHeaders().setLocation(URI.create("./login"));
                    return response.setComplete();
                }
            }
        }else{
            /*Result r = new Result();
            r.setMsg("无效token");
            r.setSuccess(false);
            return getVoidMono(exchange,r);*/
            return doRedirct(exchange,"/"+basicPath+LOGIN_URL);
        }

        return chain.filter(exchange);
    }

    /**
     * 从request里去token,顺序
     * ① cookie
     * ② header
     * ③ QueryParams
     * @param request
     * @return
     */
    private String getToken(ServerHttpRequest request){
        String authHeader=null;
        if(authHeader==null){
            MultiValueMap<String, HttpCookie> cks = request.getCookies();
            if( cks != null){
                for(String k : cks.keySet()){
                    HttpCookie ck = cks.getFirst(k);
                    if(tokenHeader.equals(ck.getName())){
                        try {
                            authHeader = URLDecoder.decode(ck.getValue(),"utf-8");
                            break;
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }

                    }
                }
            }
        }
        if(authHeader==null){
            HttpHeaders headers = request.getHeaders();
            List<String> cks = headers.get(this.tokenHeader);
            if(cks != null && cks.size()>0)
                authHeader = cks.get(0);
        }
        if (StringUtils.isBlank(authHeader)) {
            List<String> strings = request.getQueryParams().get("token");
            if (strings != null) {
                authHeader = strings.get(0);
            }
        }
        return authHeader;
    }
    /**
     * 网关抛异常
     *
     * @param body
     */
    private Mono<Void> getVoidMono(ServerWebExchange serverWebExchange, Result body) {
        serverWebExchange.getResponse().setStatusCode(HttpStatus.OK);
        serverWebExchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
        byte[] bytes = JSONObject.toJSONString(body).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverWebExchange.getResponse().bufferFactory().wrap(bytes);

        return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
    }
    /**
     * 网关抛异常
     *
     * @param body
     */
    private Mono<Void> doRedirct(ServerWebExchange serverWebExchange,String url) {
        ServerHttpResponse response = serverWebExchange.getResponse();
        //303状态码表示由于请求对应的资源存在着另一个URI，应使用GET方法定向获取请求的资源
        response.setStatusCode(HttpStatus.SEE_OTHER);
        response.getHeaders().set(HttpHeaders.LOCATION, url);
        return response.setComplete();
    }

    /**
     * 判断是否有url权限
     * @param list
     * @param url
     * @return
     */
    private boolean checkPermissions(List<ResourceInfo> list,String url){
        if(list == null || list.size() == 0){
            return false;
        }
        if(url.startsWith("/"+basicPath)){
            url = url.substring(basicPath.length()+1);
        }
        for(ResourceInfo ri : list){
            if (("0".equals(ri.getType()) || "3".equals(ri.getType())) && StrUtil.isNotBlank(ri.getUrl()) && antPathMatcher.match(ri.getUrl(), url)) {
                return true;
            }
        }
        return false;
    }
    @Override
    public int getOrder() {
        return -1000;
    }
}
